using System;
using System.Linq;
using System.Reflection;
using CommonServiceManager.Configuration;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity;

namespace UnityCommonServiceManagerAdapter.Configuration
{
	/// <summary>
	/// <see cref="IConfigurator{TImplementation}"/>
	/// </summary>
	public class UnityConfigurator<TImplementation> : ConfiguratorBase<TImplementation>
	{
		private IUnityContainer unityContainer;

		/// <summary>
		/// Create a <see cref="UnityConfigurator{TImplementation}"/>
		/// </summary>
		/// <param name="name"><see cref="ConfiguratorBase{TImplementation}(string)"/></param>
		public UnityConfigurator(string name = null)
			: base(name)
		{ }

		/// <summary>
		/// The current <see cref="IUnityContainer"/> to regster configuration in.
		/// </summary>
		public IUnityContainer Container
		{
			set
			{
				unityContainer = value;
			}
			internal get
			{
				return unityContainer ??
							 (unityContainer = ServiceLocator.GetInstance<IUnityContainer>());
			}
		}

		/// <summary>
		/// Break into <see cref="IUnityContainer"/> and extract the <see cref="PolicyList"/> for our use.
		/// </summary>
		internal PolicyList Policies
		{
			get
			{
				var unityContainerType = Container.GetType();
				var policyListField = unityContainerType.GetField("policies", BindingFlags.NonPublic | BindingFlags.Instance);
				return (PolicyList)policyListField.GetValue(Container);
			}
		}

		/// <summary>
		/// Use the <see cref="IUnityContainer"/> to set singleton behavior. <seealso cref="IConfigurator{TImplementation}.AsSingleton()"/>
		/// </summary>
		public override IConfigurator<TImplementation> AsSingleton()
		{
			if (string.IsNullOrEmpty(Name))
			{
				Container.RegisterType<TImplementation>(new ContainerControlledLifetimeManager());
				return this;
			}

			Container.RegisterType<TImplementation>(Name, new ContainerControlledLifetimeManager());
			return this;
		}

		/// <summary>
		/// Use the <see cref="IUnityContainer"/> to set an injection constructor for <typeparamref name="TImplementation"/>.
		/// </summary>
		protected override IConfigurator<TImplementation> InjectionConstructor(params KeyedType[] keyedConstructorParameterTypes)
		{
			var injectionParameters = keyedConstructorParameterTypes
				.Select(k => Tuple.Create(k.Type, k.ImplementationKey)).ToArray();

			InjectionMember injectionConstructor = new InjectionConstructorNamed(injectionParameters);

			AddPolicies(injectionConstructor);

			return this;
		}

		/// <summary>
		/// Use the <see cref="IUnityContainer"/> to set injection properties for <typeparamref name="TImplementation"/>.
		/// </summary>
		protected override IConfigurator<TImplementation> InjectionProperties(params KeyedName[] keyedInjectionPropertyNames)
		{
			var injectionMemberList = keyedInjectionPropertyNames
				.Select(property => new InjectionPropertyNamed(property.Name, property.ImplementationKey));

			foreach (var injectionPropertyNamed in injectionMemberList)
			{
				AddPolicies(injectionPropertyNamed);
			}
			return this;
		}

		private void AddPolicies(InjectionMember injectionMember)
		{
			if (string.IsNullOrEmpty(Name))
			{
				injectionMember.AddPolicies(typeof(TImplementation), Policies);
			}
			else
			{
				injectionMember.AddPolicies(null, typeof(TImplementation), Name, Policies);
			}
		}
	}
}